﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using CC.Utilities;

namespace CC.Yacht.Server
{
    public static class LobbyManager
    {
        //TODO: Implement multiple lobbies...
        //NOTE: Consider maximum users per lobby...

        #region Constructor
        static LobbyManager()
        {
            SessionManager.SessionExpired += SessionManager_SessionExpired;
        }
        #endregion

        #region Private Fields
        private static readonly Dictionary<Guid, ExtendedGameState> _Games = new Dictionary<Guid, ExtendedGameState>();
        private static readonly object _LockGames = new object();
        private static readonly object _LockUsers = new object();
        private static readonly Dictionary<long, ExtendedUser> _Users = new Dictionary<long, ExtendedUser>();
        #endregion

        #region Public Events
        public static event EventHandler<GameCountEventArgs> GameCountChanged;
        #endregion

        #region Private Event Handlers
        // ReSharper disable InconsistentNaming
        private static void SessionManager_SessionExpired(object sender, SessionExpiredEventArgs e)
        {
            if (_Users.ContainsKey(e.User.UserId))
            {
                ExtendedUser user = _Users[e.User.UserId];
                List<GameState> gameStates = GetGameStates(user);

                if (gameStates.Count > 0)
                {
                    for (int i = 0; i < gameStates.Count; i++)
                    {
                        InternalLeaveGame(gameStates[0].GameId, user);
                    }
                }

                LeaveLobby(Guid.Empty, e.User.UserId);
            }
        }
        // ReSharper restore InconsistentNaming
        #endregion

        #region Private Methods
        /// <summary>
        /// Creates a new game. Should only be used with an <see cref="ExtendedUser"/> from <see cref="SessionManager"/>
        /// </summary>
        /// <param name="lobbyId">The <see cref="Guid"/> of the lobby to join.</param>
        /// <param name="extendedUser">The <see cref="ExtendedUser"/> creating the game.</param>
        /// <returns>The GameId <see cref="Guid"/> if successfully created; <see cref="Guid.Empty"/> if not</returns>
        private static Guid InternalCreateGame(Guid lobbyId, ExtendedUser extendedUser)
        {
            Guid returnValue = Guid.Empty;

            if (extendedUser != null)
            {
                lock (_LockGames)
                {
                    GameState existingGame = GetGameState(extendedUser);

                    if (existingGame != null)
                    {
                        UpdateGameUser(existingGame.GameId, extendedUser);
                        returnValue = existingGame.GameId;
                    }
                    else
                    {
                        ExtendedGameState gameState = new ExtendedGameState(extendedUser, lobbyId);

                        _Games.Add(gameState.GameId, gameState);

                        OnGameAdded(lobbyId, gameState.ToGame());
                        OnGameCountChanged();

                        returnValue = gameState.GameId;
                    }
                }
            }

            return returnValue;
        }

        /// <summary>
        /// Joins a user to a game. Should only be used with an <see cref="ExtendedUser"/> from <see cref="SessionManager"/>
        /// </summary>
        /// <param name="gameId">The <see cref="Guid"/> on the game to join.</param>
        /// <param name="extendedUser">The <see cref="ExtendedUser"/> joining the lobby.</param>
        /// <returns>true if successfully joined; false if not</returns>
        private static bool InternalJoinGame(Guid gameId, ExtendedUser extendedUser)
        {
            bool returnValue = false;

            if (extendedUser != null)
            {
                lock (_LockGames)
                {
                    GameState existingGame = GetGameState(extendedUser);

                    if (existingGame != null)
                    {
                        if (existingGame.GameId == gameId)
                        {
                            UpdateGameUser(existingGame.GameId, extendedUser);

                            returnValue = true;
                        }
                    }
                    else
                    {
                        if (_Games.ContainsKey(gameId) && _Games[gameId].Join(extendedUser))
                        {
                            OnGameChanged(_Games[gameId].ToGame());

                            returnValue = true;
                        }
                    }
                }
            }

            return returnValue;
        }

        /// <summary>
        /// Joins a user to the lobby. Should only be used with an <see cref="ExtendedUser"/> from <see cref="SessionManager"/>
        /// </summary>
        /// <param name="lobbyId">The <see cref="Guid"/> of the lobby to join.</param>
        /// <param name="extendedUser">The <see cref="ExtendedUser"/> joining the lobby.</param>
        /// <returns>true if successfully joined; false if not</returns>
        private static bool InternalJoinLobby(Guid lobbyId, ExtendedUser extendedUser)
        {
            bool returnValue = false;

            if (extendedUser != null)
            {
                lock (_LockUsers)
                {
                    if (!_Users.ContainsKey(extendedUser.UserId))
                    {
                        _Users.Add(extendedUser.UserId, extendedUser);

                        OnJoinLobby(lobbyId, extendedUser);
                    }
                    else
                    {
                        _Users[extendedUser.UserId] = extendedUser;
                    }

                    returnValue = true;
                }
            }

            return returnValue;
        }

        private static bool InternalLeaveGame(Guid gameId, ExtendedUser extendedUser)
        {
            bool returnValue = false;

            if (extendedUser != null)
            {
                lock (_LockGames)
                {
                    if (_Games.ContainsKey(gameId) && _Games[gameId].Leave(extendedUser))
                    {
                        ExtendedGameState gameState = _Games[gameId];

                        if (gameState.Players.Count > 0)
                        {
                            OnGameChanged(gameState.ToGame());
                        }
                        else
                        {
                            _Games.Remove(gameId);

                            OnGameRemoved(gameState.LobbyId, gameState.ToGame());
                            OnGameCountChanged();                            
                        }

                        returnValue = true;
                    }
                }
            }

            return returnValue;
        }

        private static void OnGameAdded(Guid lobbyId, Game game)
        {
            foreach (var tempUser in _Users.Values)
            {
                if (tempUser.Callback != null)
                {
                    try
                    {
                        tempUser.Callback.LobbyGameAdded(game);
                    }
                    catch (CommunicationException exception)
                    {
                        tempUser.Callback = null;
                        Logging.LogException(exception);
                    }
                }
            }            
        }

        private static void OnGameChanged(Game game)
        {
            foreach (var tempUser in _Users.Values)
            {
                if (tempUser.Callback != null)
                {
                    try
                    {
                        tempUser.Callback.LobbyGameChanged(game);
                    }
                    catch (CommunicationException exception)
                    {
                        tempUser.Callback = null;
                        Logging.LogException(exception);
                    }
                }
            }
        }

        private static void OnGameCountChanged()
        {
            if (GameCountChanged != null)
            {
                GameCountChanged(null, new GameCountEventArgs(_Games.Count));
            }
        }

        private static void OnGameRemoved(Guid lobbyId, Game game)
        {
            foreach (var tempUser in _Users.Values)
            {
                if (tempUser.Callback != null)
                {
                    try
                    {
                        tempUser.Callback.LobbyGameRemoved(game);
                    }
                    catch (CommunicationException exception)
                    {
                        tempUser.Callback = null;
                        Logging.LogException(exception);
                    }
                }
            }
        }
        
        private static void OnJoinLobby(Guid lobbyId, ExtendedUser user)
        {
            foreach (var tempUser in _Users.Values)
            {
                if (tempUser.Callback != null)
                {
                    try
                    {
                        tempUser.Callback.LobbyUserJoined(user.ToUser());
                    }
                    catch (CommunicationException exception)
                    {
                        tempUser.Callback = null;
                        Logging.LogException(exception);
                    }
                }
            }
        }

        private static void OnLeaveLobby(Guid lobbyId, ExtendedUser user)
        {
            foreach (var tempUser in _Users.Values)
            {
                if (tempUser.Callback != null)
                {
                    try
                    {
                        tempUser.Callback.LobbyUserLeft(user.ToUser());
                    }
                    catch (CommunicationException exception)
                    {
                        tempUser.Callback = null;
                        Logging.LogException(exception);
                    }
                }
            }
        }

        private static void OnSendLobbyMessage(Guid lobbyId, ExtendedUser user, string message)
        {
            if (!string.IsNullOrEmpty(message))
            {
                foreach (var tempUser in _Users.Values)
                {
                    if (tempUser.Callback != null)
                    {
                        try
                        {
                            tempUser.Callback.LobbyMessageReceived(user.ToUser(), message);
                        }
                        catch (CommunicationException exception)
                        {
                            tempUser.Callback = null;
                            Logging.LogException(exception);
                        }
                    }
                }
            }
        }

        private static void UpdateGameUser(Guid gameId, ExtendedUser user)
        {
            if (_Games.ContainsKey(gameId))
            {
                ExtendedGameState gameState = _Games[gameId];

                if (user.Equals(gameState.CurrentUser))
                {
                    gameState.Owner = user;
                }

                if (user.Equals(gameState.Owner))
                {
                    gameState.Owner = user;
                }

                if (gameState.Players.Contains(user))
                {
                    int index = gameState.Players.IndexOf(user);
                    gameState.Players[index] = user;
                }
            }
        }
        #endregion

        #region Public Methods
        public static Guid CreateGame(Guid lobbyId, ExtendedUser user)
        {
            return InternalCreateGame(lobbyId, user);
        }

        public static GameState GetGameState(ExtendedUser user)
        {
            GameState returnValue = null;
            List<GameState> gameStates = GetGameStates(user);

            if (gameStates.Count > 0)
            {
                returnValue = gameStates[0];
            }

            return returnValue;
        }

        public static GameState GetGameState(Guid gameId)
        {
            return _Games[gameId].ToGameState();
        }

        public static List<GameState> GetGameStates(ExtendedUser user)
        {
            return _Games.Where(keyValuePair => keyValuePair.Value.Players.Contains(user)).Select(keyValuePair => keyValuePair.Value.ToGameState()).ToList();
        }

        public static List<Game> GetGames(Guid lobbyId)
        {
            return _Games
                .Select(game => game.Value.ToGame())
                .ToList();
        }

        public static List<User> GetUsers(Guid lobbyId)
        {
            return _Users
                .Select(user => user.Value.ToUser())
                .OrderBy(simpleUser => simpleUser.Username)
                .ToList();    
        }

        public static bool JoinGame(Guid gameId, ExtendedUser user)
        {
            return InternalJoinGame(gameId, user);
        }

        public static bool JoinLobby(Guid lobbyId, ExtendedUser user)
        {
            return InternalJoinLobby(lobbyId, user);
        }

        public static bool LeaveGame(Guid gameId, ExtendedUser user)
        {
            return InternalLeaveGame(gameId, user);
        }

        public static bool LeaveLobby(Guid lobbyId, Guid sessionId)
        {
            bool returnValue = false;

            ExtendedUser tempUser = SessionManager.GetUser(sessionId);
            if (tempUser != null)
            {
                returnValue = LeaveLobby(lobbyId, tempUser.UserId);
            }

            return returnValue;
        }

        public static bool LeaveLobby(Guid lobbyId, long userId)
        {
            bool returnValue = false;

            lock (_LockUsers)
            {
                if (_Users.ContainsKey(userId))
                {
                    ExtendedUser tempUser = _Users[userId];
                    _Users.Remove(userId);

                    OnLeaveLobby(lobbyId, tempUser);

                    returnValue = true;
                }
            }

            return returnValue;
        }

        public static bool Roll(Guid gameId, ExtendedUser user)
        {
            bool returnValue = false;

            //NOTE: Do I need a lock?
            if (user != null && _Games.ContainsKey(gameId))
            {
                returnValue = _Games[gameId].Roll(user);
            }

            return returnValue;
        }

        public static bool SendMessage(Target target, Guid targetId, ExtendedUser user, string message)
        {
            bool returnValue = false;

            if (user != null)
            {
                switch (target)
                {
                    case Target.Game:
                        {
                            //NOTE: Do I need a lock?
                            if (_Games.ContainsKey(targetId))
                            {
                                returnValue = _Games[targetId].SendMessage(user, message);
                            }
                            break;
                        }
                    case Target.Lobby:
                        {
                            //NOTE: Do I need a lock?
                            if (_Users.ContainsKey(user.UserId))
                            {
                                OnSendLobbyMessage(targetId, user, message);
                                returnValue = true;
                            }
                            break;
                        }
                }
            }

            return returnValue;
        }

        public static bool SetScoreField(Guid gameId, ExtendedUser user, ScoreField scoreField)
        {
            return (user != null && _Games.ContainsKey(gameId) && _Games[gameId].SetScoreField(user, scoreField));
        }

        public static bool StartGame(Guid gameId, ExtendedUser user)
        {
            bool returnValue = false;

            if (user != null && _Games.ContainsKey(gameId) && _Games[gameId].Start(user))
            {
                OnGameChanged(_Games[gameId].ToGame());

                returnValue = true;
            }

            return returnValue;
        }

        public static bool UpdateDice(Guid gameId, ExtendedUser user, Dice dice)
        {
            return (user != null && _Games.ContainsKey(gameId) && _Games[gameId].UpdateDice(user, dice));
        }
        #endregion
    }
}
